Skip to content

ci(event-names): add cross-repo EVENT_NAMES sync gate#101

Closed
daniloleonecarneiro wants to merge 2 commits into
developfrom
danilocarneiro/evo-1241-freeze-evoflow-event-names
Closed

ci(event-names): add cross-repo EVENT_NAMES sync gate#101
daniloleonecarneiro wants to merge 2 commits into
developfrom
danilocarneiro/evo-1241-freeze-evoflow-event-names

Conversation

@daniloleonecarneiro
Copy link
Copy Markdown
Contributor

@daniloleonecarneiro daniloleonecarneiro commented May 21, 2026

EVO-1241 — Cross-repo EVENT_NAMES sync gate (monorepo)

Terceira e última PR coordenada da story 7.5. CRM agora raise em event_name desconhecido; evo-flow agora 400 em event / eventName desconhecido. Esta PR adiciona o terceiro pilar: um job de CI que bloqueia o merge de qualquer PR que mexa em um lado da lista canônica sem mexer no outro.

Sem este gate, alguém adicionando 'order.placed' só no CRM (ou só no TS) ia mergear, e o sistema voltava a ter silent data gap em produção até alguém perceber.

Mudanças

Arquivo Mudança
scripts/check-event-names-sync.sh (novo, chmod +x) Pure Bash, sem deps de runtime. Extrai a lista do Ruby (%w[ ... ] via awk scoped) e do TS (EVENT_NAMES = [ ... ] via awk scoped + grep -oE "'...'"), sort -u em ambos, diff -u. Exit 0 em match + EXPECTED_COUNT=16. Exit 1 com diff (DIVERGENT) caso contrário.
.github/workflows/ci.yml Job novo event-names-sync. submodules: recursive + fetch-depth: 0 (precisa do diff base...head). Step Detect relevant changes faz short-circuit quando nenhum arquivo relevante mudou no PR — não custa nada em PRs de docker/frontend.

Como o script lida com falsos positivos

Antes do review adversarial:

  • Lado TS extraía com grep -oE "'[a-z][a-z._]*'" em todo o arquivo. Um comentário tipo // see legacy 'foo.bar' seria contado como evento canônico. Comentário no script afirmava "EVENT_NAMES is the only such block" — mas o regex não enforçava.

Agora:

  • Lado TS faz awk scoped no bloco EVENT_NAMES = [] antes do grep. Stray quoted strings em comentários ou outras consts são ignorados.
  • Verificado em 3 paths: happy (OK), divergent (rename de um entry → DIVERGENT com diff + exit 1), scope (adiciona 'stray.name' em comentário → OK, não conta).

EXPECTED_COUNT — por quê

Hard-coded em 16. Detecta o caso patológico em que alguém adiciona um entry em ambos os lados simultaneamente mas o restante do PR não toca a story 7.5 — i.e., crescer a lista sem coordenação com integration. Ao bumpar de 16 → 17, o autor é forçado a editar o script no mesmo commit, o que dispara revisão.

Trade-off: quando a lista cresce legitimamente, é uma terceira edição obrigatória (Ruby + TS + script). Documentado em ambos os READMEs (CRM + evo-flow).

CI — short-circuit

- name: Detect relevant changes
  id: changed
  run: |
    base="\${{ github.event.pull_request.base.sha }}"
    head="\${{ github.event.pull_request.head.sha }}"
    if git diff --name-only "\$base...\$head" | grep -E '^(evo-ai-crm-community/lib/events/|evo-flow/src/modules/events/|scripts/check-event-names-sync\.sh|\.github/workflows/ci\.yml)' >/dev/null; then
      echo "run=true" >> "\$GITHUB_OUTPUT"
    else
      echo "run=false" >> "\$GITHUB_OUTPUT"
      echo "No event-names-related files changed — skipping sync check."
    fi

Filtro em job-level (paths: no workflow) não cabe porque o workflow guarda validate-compose e lint-dockerfiles também — não dá pra restringir o trigger sem desligar os outros. Solução: short-circuit por step.

Validação local

$ bash scripts/check-event-names-sync.sh
event_names_sync: OK (16 entries)
# exit 0

# Forçando divergence:
$ sed -i "s/'contact.created'/'contact.created.STRAY'/" \
    evo-flow/src/modules/events/event-names.enum.ts
$ bash scripts/check-event-names-sync.sh
event_names_sync: DIVERGENT — see diff below
  (--- Ruby: .../evo_flow_event_names.rb)
  (+++ TS:   .../event-names.enum.ts)
--- /tmp/...
+++ /tmp/...
@@ -2,7 +2,6 @@
 ...
-contact.created
 ...
# exit 1
Check Resultado
Happy path (16 entries match) OK exit 0
Divergent (rename 1 entry) DIVERGENT exit 1 com diff
Scope test (stray quoted string em comentário) OK exit 0 (ignorado)
YAML lint do workflow (python3 -c 'import yaml; ...') Jobs validate-compose, lint-dockerfiles, event-names-sync

PRs coordenadas (mesma branch)

Merge order: CRM → evo-flow → esta PR. O gate só roda meaningfully depois das duas primeiras mergeadas (precisa dos arquivos canônicos atualizados em develop de cada submodule).

Out of Scope

  • Filtro paths: no nível do workflow (interferiria com validate-compose / lint-dockerfiles). Short-circuit por step cobre o caso.
  • Auto-derivar EXPECTED_COUNT de git log (overkill).
  • Submodule pointer bumps no superprojeto (intencional — só PRs nas branches de develop dos submodules; sem ponteiro até as 3 mergearem).

Test plan

  • CI roda este job verde no primeiro PR depois do merge das duas primeiras.
  • Confirmar que adicionar um entry só em um lado bloqueia o merge (test artificial em fork ou via PR de prova).
  • Documentar no CHANGELOG da monorepo se houver.

🤖 Generated with Claude Code

Summary by Sourcery

Add a CI gate to enforce synchronization between the canonical CRM Ruby event names list and the evo-flow TypeScript mirror.

New Features:

  • Introduce a Bash script that validates the Ruby and TypeScript event name lists stay in sync and match an expected count.

Enhancements:

  • Add a dedicated CI job that runs the event-names sync script and short-circuits when no relevant files have changed.

CI:

  • Extend the main CI workflow with an event-names sync job that checks out submodules, detects relevant changes, and runs the sync verification script.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 21, 2026

Reviewer's Guide

Adds a Bash-based CI gate to ensure the canonical EVENT_NAMES lists in the CRM Ruby code and evo-flow TypeScript stay synchronized, and wires it into the GitHub Actions workflow with a change-detection short-circuit and a hard-coded expected count to force coordinated updates.

File-Level Changes

Change Details Files
Introduce a Bash sync-check script that compares the canonical EVENT_NAMES lists between the CRM Ruby source and evo-flow TypeScript source and enforces an expected total count.
  • Add executable script that locates the Ruby %w[...] block defining EvoFlow::EVENT_NAMES and extracts a sorted unique list via awk and basic text processing
  • Add logic to scope parsing of the TypeScript EVENT_NAMES = [ ... ] block before extracting single-quoted event names to avoid counting stray strings or comments
  • Compare the normalized Ruby and TypeScript lists via diff -u, emitting a human-readable diff and exiting with status 1 when lists diverge
  • Enforce a hard-coded EXPECTED_COUNT of 16, failing when the lists match each other but not this size, and implement error handling for missing source files with distinct exit codes
scripts/check-event-names-sync.sh
Wire the event-names sync script into GitHub Actions CI as a dedicated job that only runs when relevant files change.
  • Add event-names-sync job to the main CI workflow running on ubuntu-latest
  • Configure checkout with recursive submodules and full history (fetch-depth: 0) so the job can diff PR base and head
  • Add a Detect relevant changes step that uses git diff --name-only base...head plus a grep filter to decide whether to run the sync check, writing a run=true/false output
  • Gate execution of the sync script step on the changed output to short-circuit the job when no event-names-related files were touched
.github/workflows/ci.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The Detect relevant changes step relies on github.event.pull_request.base.sha/head.sha, which will be empty on non-PR events; consider adding an if: github.event_name == 'pull_request' guard on the job or step to avoid failures if the workflow is ever reused for push triggers.
  • The hard-coded EXPECTED_COUNT=16 in check-event-names-sync.sh introduces a third location to touch on every legitimate list change; you might consider deriving the expected count from one canonical side (e.g., Ruby) or from a dedicated config file to avoid having to remember to bump this constant manually.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `Detect relevant changes` step relies on `github.event.pull_request.base.sha`/`head.sha`, which will be empty on non-PR events; consider adding an `if: github.event_name == 'pull_request'` guard on the job or step to avoid failures if the workflow is ever reused for push triggers.
- The hard-coded `EXPECTED_COUNT=16` in `check-event-names-sync.sh` introduces a third location to touch on every legitimate list change; you might consider deriving the expected count from one canonical side (e.g., Ruby) or from a dedicated config file to avoid having to remember to bump this constant manually.

## Individual Comments

### Comment 1
<location path="scripts/check-event-names-sync.sh" line_range="58-59" />
<code_context>
+      if (match(line, /\]/)) { print substr(line, 1, RSTART - 1); capture = 0; exit }
+      print line
+    }
+  ' "$TS_FILE" | grep -oE "'[a-z][a-z._]*'" | tr -d "'" | sort -u
+)
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The TS event-name regex is very restrictive and will silently ignore many valid identifier shapes.

`grep -oE "'[a-z][a-z._]*'"` only matches lowercase letters plus `.` and `_`, so any event name with digits, hyphens, uppercase letters, etc. is excluded from `ts_list`. That can mask divergence between the TS file and the other list. Please broaden the regex (e.g. `"'[-A-Za-z0-9._]+'"`) or otherwise match the full set of valid event-name characters so the check fails whenever either file adds an out-of-pattern name.

```suggestion
  ' "$TS_FILE" | grep -oE "'[-A-Za-z0-9._]+'" | tr -d "'" | sort -u
)
```
</issue_to_address>

### Comment 2
<location path="scripts/check-event-names-sync.sh" line_range="37-46" />
<code_context>
+
+# Ruby side: scope to the %w[ ... ] block, then tokenise on whitespace.
+ruby_list=$(
+  awk '
+    /%w\[/ { capture = 1; sub(/.*%w\[/, "") }
+    capture {
+      line = $0
+      if (sub(/\].*/, "", line)) { print line; capture = 0; exit }
+      print line
+    }
+  ' "$RUBY_FILE" | tr -s '[:space:]' '\n' | sed '/^$/d' | sort -u
+)
+
+# TS side: scope to the EVENT_NAMES = [ ... ] block, THEN extract single-quoted
+# literals. Anchoring to the block prevents a stray single-quoted string in a
+# comment, JSDoc, or future export from leaking into the canonical set.
+ts_list=$(
+  awk '
+    /EVENT_NAMES[[:space:]]*=[[:space:]]*\[/ { capture = 1 }
+    capture {
+      line = $0
</code_context>
<issue_to_address>
**suggestion:** TS block detection for `EVENT_NAMES` does not handle type annotations or alternative spacing around the `=`.

The current awk guard `/EVENT_NAMES[[:space:]]*=[[:space:]]*\/` only matches when `EVENT_NAMES` is immediately followed by `=` and `[`. With a type annotation or inline comment (e.g. `export const EVENT_NAMES: readonly string[] = [`), the match fails and the script sees an empty list, causing confusing sync failures. Consider allowing arbitrary chars between the name and `=` (e.g. `/EVENT_NAMES[^=]*=[[:space:]]*\[/`) so annotations/comments don’t break extraction.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +58 to +59
' "$TS_FILE" | grep -oE "'[a-z][a-z._]*'" | tr -d "'" | sort -u
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): The TS event-name regex is very restrictive and will silently ignore many valid identifier shapes.

grep -oE "'[a-z][a-z._]*'" only matches lowercase letters plus . and _, so any event name with digits, hyphens, uppercase letters, etc. is excluded from ts_list. That can mask divergence between the TS file and the other list. Please broaden the regex (e.g. "'[-A-Za-z0-9._]+'") or otherwise match the full set of valid event-name characters so the check fails whenever either file adds an out-of-pattern name.

Suggested change
' "$TS_FILE" | grep -oE "'[a-z][a-z._]*'" | tr -d "'" | sort -u
)
' "$TS_FILE" | grep -oE "'[-A-Za-z0-9._]+'" | tr -d "'" | sort -u
)

Comment on lines +37 to +46
awk '
/%w\[/ { capture = 1; sub(/.*%w\[/, "") }
capture {
line = $0
if (sub(/\].*/, "", line)) { print line; capture = 0; exit }
print line
}
' "$RUBY_FILE" | tr -s '[:space:]' '\n' | sed '/^$/d' | sort -u
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: TS block detection for EVENT_NAMES does not handle type annotations or alternative spacing around the =.

The current awk guard /EVENT_NAMES[[:space:]]*=[[:space:]]*\/ only matches when EVENT_NAMES is immediately followed by = and [. With a type annotation or inline comment (e.g. export const EVENT_NAMES: readonly string[] = [), the match fails and the script sees an empty list, causing confusing sync failures. Consider allowing arbitrary chars between the name and = (e.g. /EVENT_NAMES[^=]*=[[:space:]]*\[/) so annotations/comments don’t break extraction.

Adds scripts/check-event-names-sync.sh — pure Bash, no Ruby/Node runtime,
that extracts the canonical event-names list from
evo-ai-crm-community/lib/events/evo_flow_event_names.rb and the TS mirror
in evo-flow/src/modules/events/event-names.enum.ts, sorts and diffs them.

Both sides use scoped extraction (awk anchored to the %w[ ] block on Ruby
and EVENT_NAMES = [ ] on TS) so a stray quoted string in a comment cannot
leak in. EXPECTED_COUNT=16 catches the case where both sides grow to the
same wrong size in lockstep — bump it in the same PR that adds entries.
@daniloleonecarneiro daniloleonecarneiro force-pushed the danilocarneiro/evo-1241-freeze-evoflow-event-names branch from de23a0c to 3b51c87 Compare May 21, 2026 01:04
@daniloleonecarneiro daniloleonecarneiro changed the title ci(event-names): add cross-repo EVENT_NAMES sync gate (EVO-1241) ci(event-names): add cross-repo EVENT_NAMES sync gate May 21, 2026
@dpaes
Copy link
Copy Markdown

dpaes commented May 21, 2026

Code review — EVO-1241 (cross-repo sync gate)

Script logic is correct (AC5 ✅): Ruby %w[…] scoped via awk + TS EVENT_NAMES = […] scoped via awk then '…' extraction, sort -u on both, diff -u, EXPECTED_COUNT=16. Extraction failures fail safe (DIVERGENT). But there is one significant gap:

🔴 HIGH — the gate is skipped on exactly the PRs that change event names. The Detect relevant changes step filters with grep -E '^(evo-ai-crm-community/lib/events/|evo-flow/src/modules/events/|…)'. Event-names files live inside submodules, and in the monorepo a submodule bump shows up as the bare directory in the diff, not the nested path. Proven against a real bump commit in this repo:

$ git diff --name-only d7b8ebf~1 d7b8ebf
evo-ai-crm-community
evo-flow
...
→ regex NO MATCH → run=false → sync check SKIPPED

So on a pointer-bump PR (the real path event names travel through), run=false and the script never runs — AC6 ("a PR that adds an event_name on only one side → CI fails") is effectively unenforced in that flow. Fix options: include the submodule directories in detection (^evo-ai-crm-community($|/), ^evo-flow($|/)), or simply always run the script (~5s). Whether this is a blocker depends on the intended team workflow — open question for Davidson.

CI context: the 3 red checks on this PR (sync, dockerfiles, compose) are not caused by this PR's code — they all die at actions/checkout with fatal: No url found for submodule path '.claude', which is exactly what #102 fixes (#102 passes those same Docker jobs). This PR cannot go green until #102 merges and the submodule pointers are bumped to include #90 + #3.

🟢 LOW — the CRM README (#90) does not mention the EXPECTED_COUNT "third edit" rule; the evo-flow README (#3) does. Doc asymmetry.

No verdict here — awaiting Davidson's decision (including the AC6 workflow question above).

The previous "Detect relevant changes" gate filtered the diff by path
prefix (`^evo-ai-crm-community/lib/events/`, `^evo-flow/src/modules/events/`),
which is the exact pattern `git diff --name-only` does NOT emit for the
workflow that matters most here: a submodule-pointer bump. Bumps appear
as a bare directory line (`evo-ai-crm-community`, `evo-flow`), so the
gate evaluated to `run=false` and the sync check was silently skipped
precisely when one side moved to a new EVENT_NAMES list and the other
did not — defeating AC6 of the story.

Drop the gate entirely. The script is pure bash and finishes in ~5s, so
the cost of running unconditionally is negligible compared to the cost of
chasing regex coverage for every diff shape git can emit.
@dpaes
Copy link
Copy Markdown

dpaes commented May 21, 2026

Code review — EVO-1241 — Round 2 (delta vs the 03:03 review)

The high finding from round 1 is resolved:

  • H-1 — sync gate was skipped on exactly the PRs that move event-names (submodule bumps). The path-filter "Detect relevant changes" step is gone; the event-names-sync job now runs unconditionally (actions/checkout@v4 with submodules: recursive, then bash scripts/check-event-names-sync.sh). The inline comment documents the original failure mode precisely. A bump PR — the case git diff --name-only emits as a bare submodule directory that the old regex never matched — now executes the script against the pinned submodule contents. Verified the lists are 16 ↔ 16 in sync from the PR heads, and EXPECTED_COUNT=16 matches. Script logic (Ruby %w[…] awk-scoped, TS block-scoped extraction, diff + count guard) is unchanged from what I validated in round 1.

Two residuals on AC6 — restating so "H-1 resolved" isn't mistaken for "AC6 fully enforced":

🟢 L-3 (EXPECTED_COUNT doc) was addressed on evo-ai-crm #90's README (+ evo-flow already had it). The monorepo README still doesn't restate the rule — minor, optional.

No verdict from me — consolidated status is on the Linear card.

Copy link
Copy Markdown

@dpaes dpaes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved (round-2 code review).

H-1 is resolved — the path-filter gate is gone and event-names-sync now runs unconditionally (submodules: recursive + the bash script), so a submodule-bump PR (the case that was being skipped) is now covered. Script logic re-verified; lists 16 ↔ 16, EXPECTED_COUNT=16 matches.

Two residuals noted on the Linear card so "H-1 resolved" isn't read as "AC6 fully enforced":

  • ⚠️ For the gate to actually block merge, branch protection on develop must list EvoFlow event-names sync as a required check (repo setting, outside this diff).
  • ⚠️ This job only goes green after #102 merges (submodules: recursive trips on the orphan .claude gitlink until then) and after the submodule pointers are bumped. Merge last in the chain.

@dpaes
Copy link
Copy Markdown

dpaes commented May 21, 2026

Status update: #102, #90 and #3 are merged. This PR (#101) is approved but intentionally held open — the submodule-pointer bump was excluded from this pass, so the event-names-sync job stays red (it resolves the canonical files from the pinned submodule commits, which still point pre-merge).

To finish the chain: bump the evo-ai-crm-community and evo-flow submodule pointers in the monorepo to the freshly-merged develop commits, confirm EvoFlow event-names sync goes green, then merge this PR. Owned by the release coordination, not this review pass.

@dpaes
Copy link
Copy Markdown

dpaes commented May 22, 2026

Closing this PR per Davidson's decision: the cross-repo CI sync gate is not needed right now. The CI job and the version bump will be addressed later in a follow-up.

@dpaes dpaes closed this May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants